home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacHack 1994
/
MacHack 1994.toast
/
MacHack™94
/
Talks & Papers
/
Michael D. Crawford↵
/
Word Services SDK 1.0.5
/
Writeswell Jr. Source
/
ObText.c
< prev
next >
Wrap
Text File
|
1993-03-17
|
23KB
|
969 lines
/* ObText.c
* ©1992 Working Software, Inc.
* This source code is copyrighted. Permission is granted to use the Word Services
* portion of the Writeswell Jr. source code in your own programs, but you
* may not distribute the Writeswell Jr. word-processor code as a
* commercial product. If you modify the code, please do not call it
* Writeswell Jr. (or Writeswell.) This will ensure that people understand the
* program and don’t have to deal with a number of different versions with
* who-knows-what going on in the code.
*
* Writeswell Jr. and Writeswell are trademarks of Working Software, Inc.
* 26 Dec 91 Mike Crawford
*/
#include <AppleEvents.h>
#include <AEObjects.h>
#include <AEPackObject.h>
#include <AERegistry.h>
#include "AppEvents.h"
#include "WordServices.h"
#include "ObWind.h"
#include "ObText.h"
#include "Gripe.h"
#include "Scroll.h"
#include "TBConstants.h"
#include "TBGlobals.h"
/* Given the the direct object of an event is a TextEdit text token (or property thereof),
* do the requested event.
*/
OSErr DispatchTEText( AEDesc *tokenPtr,
AppleEvent *theAppleEventPtr,
AppleEvent *replyEventPtr,
long refCon )
{
OSErr err;
AEEventClass theClass;
AEEventID theID;
/* This function is only for the Core suit. Get the event ID from the appleEvent
*/
err = GetEventID( theAppleEventPtr, &theID );
switch ( theID ){
case kAEGetData:
err = TETextGetDataHandler( tokenPtr, theAppleEventPtr, replyEventPtr, refCon );
break;
case kAESetData:
err = TETextSetDataHandler( tokenPtr, theAppleEventPtr, replyEventPtr, refCon );
break;
default:
err = errAEEventNotHandled;
break;
}
return noErr;
}
/*
* Event Handlers
*/
/* Get Data */
OSErr TETextGetDataHandler( AEDesc *tokenPtr,
AppleEvent *theAppleEventPtr,
AppleEvent *replyEventPtr,
long refCon )
{
TEHandle textH;
DescType propCode;
short startPos;
short length;
TETextTokenBody **tokHdl;
Handle rawTextHdl;
AEDesc replyValue;
long rawSize;
OSErr err;
SignedByte hState;
/* Sanity check */
if ( tokenPtr->descriptorType != typeTEText ){
Gripe( "\pGot wrong token type" );
return errAEEventNotHandled;
}
tokHdl = (TETextTokenBody**)(tokenPtr->dataHandle);
textH = (*tokHdl)->textH;
propCode = (*tokHdl)->propertyCode;
startPos = (*tokHdl)->startPos;
length = (*tokHdl)->length;
if ( !textH ){
Gripe( "\pAttempting to get data for non-existent TextEdit Record" );
return errAENoSuchObject;
}
switch ( propCode ){
case typeNull:
/* This is a magic number for "Not A Property". I don't know if this
* is really kosher - gotta ask, but it is a convenience.
*/
/* return all of the text in the specified range */
rawTextHdl = (Handle)TEGetText( textH );
/* Check the range to be sure the text is really there */
rawSize = GetHandleSize( rawTextHdl );
if ( rawSize < startPos + length ){
return errAECorruptData;
}
hState = HGetState( rawTextHdl );
HLock( rawTextHdl );
err = AECreateDesc( typeChar,
(char*)*rawTextHdl + startPos,
(Size)length,
&replyValue );
HSetState( rawTextHdl, hState );
if ( err ){
Gripe( "\pAECreateDesc failed to create text replyValue" );
return err;
}
break;
case pClass:
case pColor:
case pFont:
case pPointSize:
case pScriptTag:
case pTextStyles:
Gripe( "\pGot a property type we do not yet implement" );
return errAENoSuchObject;
break;
default:
Gripe( "\pUnknown property type" );
return errAENoSuchObject;
break;
}
/* At this point we have some kind of descriptor to stick in the reply */
err = AEPutParamDesc( replyEventPtr,
keyDirectObject,
&replyValue );
if ( err ){
Gripe( "\pAEPutParamDesc failed" );
return err;
}
err = AEDisposeDesc( &replyValue );
if ( err ){
Gripe( "\pAEDisposeDesc failed" );
return err;
}
return noErr;
}/* TETextGetDataHandler */
/* Set Data */
OSErr TETextSetDataHandler( AEDesc *tokenPtr,
AppleEvent *theAppleEventPtr,
AppleEvent *replyEventPtr,
long refCon )
{
TEHandle textH;
DescType propCode;
short startPos;
short length;
TETextTokenBody **tokHdl;
AEDesc newValue;
AEDesc textValue;
AEDesc boolValue;
SignedByte hState;
long newTextLen;
AEDesc propValToken;
OSErr err;
/* Sanity check */
if ( tokenPtr->descriptorType != typeTEText ){
Gripe( "\pGot wrong token type" );
return errAEEventNotHandled;
}
tokHdl = (TETextTokenBody**)(tokenPtr->dataHandle);
textH = (*tokHdl)->textH;
propCode = (*tokHdl)->propertyCode;
startPos = (*tokHdl)->startPos;
length = (*tokHdl)->length;
if ( !textH ){
Gripe( "\pAttempting to set data for non-existent TextEdit Record" );
return errAENoSuchObject;
}
/* Get the value to set, whatever it is */
err = AEGetParamDesc( theAppleEventPtr,
keyAEData,
typeWildCard,
&newValue );
if ( err ){
Gripe( "\pAEGetParamDesc failed to get keyAEData" );
return err;
}
switch ( propCode ){
case typeNull:
/* This is a magic number for "Not A Property". I don't know if this
* is really kosher - gotta ask, but it is a convenience.
*/
/* Set the data in the specified range */
/* STUB Look for the optional parameter to see where to actually
* set the text at
*/
err = AECoerceDesc( &newValue, typeChar, &textValue );
if ( err ){
Gripe( "\pAECoerceDesc failed to coerce to text" );
return err;
}
TESetSelect( (long)startPos, (long)startPos + length, textH );
TEDelete( textH );
newTextLen = GetHandleSize( textValue.dataHandle );
hState = HGetState( textValue.dataHandle );
HLock( textValue.dataHandle );
TEInsert( (Ptr)(*textValue.dataHandle), newTextLen, textH );
gDocDirty = true;
HSetState( textValue.dataHandle, hState );
err = AEDisposeDesc( &textValue );
if ( err ){
Gripe( "\pAEDisposeDesc failed" );
return err;
}
break;
case pBackgroundHilite:
/* Make sure that we have a boolean value */
err = AECoerceDesc( &newValue, typeBoolean, &boolValue );
if ( err ){
Gripe( "\pAECoerceDesc failed to coerce to typeBoolean" );
return err;
}
/* Set the selection in the TextEdit text */
TESetSelect( startPos,
startPos + length,
textH );
if ( *(Boolean*)(*boolValue.dataHandle) ){
/* Display the selection, given that we are in the background */
ShowSelection( textH );
/* STUB we should get the window from the token */
SetVertScroll( gDocWindow, gVertScroll );
TEActivate( textH ); /* (Not normally done while in background) */
}else{
/* STUB This isn't really the best thing to do. What we really should
* do is check to see whether we are the front process, and deactivate
* if so. For this demo, we can reasonably assume that we are not
* the front process.
*/
TEDeactivate( textH );
}
return noErr;
break;
case pClass:
case pColor:
case pFont:
case pPointSize:
case pScriptTag:
case pTextStyles:
Gripe( "\pGot a property type we do not yet implement" );
return errAENoSuchObject;
break;
default:
Gripe( "\pUnknown property type" );
return errAENoSuchObject;
break;
}
/* At this point we are done with the newValue descriptor */
err = AEDisposeDesc( &newValue );
if ( err ){
Gripe( "\pAEDisposeDesc newValue failed" );
return err;
}
return noErr;
}/* TETextSetDataHandler */
/*
* Object Accessors
*/
/* Return a text token given a Window token */
pascal OSErr TextFromWind(DescType desiredClass,
const AEDesc *container,
DescType containerClass,
DescType form,
const AEDesc *selectionData,
AEDesc *theToken,
long LongInt)
{
AEDesc longKeyData;
WindowPtr wp;
long count;
TETextTokenBody tokData;
OSErr err;
/* Check that the container is what we intend. This should only happen if we
* installed the token handler incorrectly.
*/
if ( container->descriptorType != cWindow )
return errAEEventNotHandled;
/* find the window based on the key form */
switch ( form ){
case formAbsolutePosition:
/* Make sure we really have a type long descriptor */
err = AECoerceDesc( selectionData, typeLongInteger, &longKeyData );
if ( err ){
Gripe( "\pAECoerceDesc failed" );
return err;
}
count = **(long**)(longKeyData.dataHandle);
/* We're done with the descriptor created in the coercion */
err = AEDisposeDesc( &longKeyData );
if ( err ){
Gripe( "\pAEDisposeDesc failed" );
return err;
}
#ifndef HACK_OSPECS
/* We only have one text field in the window */
if ( count != 1 ){
Gripe( "\pAttempting to get text from other than the first field" );
return errAENoSuchObject;
}
#else
/* Simulate the presence of two text blocks */
if ( count != 1 && count != 2){
Gripe( "\pAttempting to get text from other than the first field" );
return errAENoSuchObject;
}
#endif
wp = (*(WindTokenBody**)(container->dataHandle))->theWindowPtr;
MakeTETextTokenBody( wp, &tokData, typeNull );
err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
if ( err ){
Gripe( "\pAECreateDesc failed to create a token" );
return err;
}
return noErr;
break;
case formRelativePosition:
case formTest:
case formRange:
case formPropertyID:
Gripe( "\pGot a formPropertyID" );
break;
case formName:
return errAEEventNotHandled; /* Flesh this out later */
break;
default:
Gripe( "\pGot unexpected key form" );
return errAEEventNotHandled;
}
return noErr;
}
void MakeTETextTokenBody( WindowPtr wp, TETextTokenBody* tokDataPtr, DescType propCode )
{
Handle rawTextHdl;
short textLen;
TEHandle textH;
textH = (TEHandle)GetWRefCon( wp );
rawTextHdl = (Handle)TEGetText( textH );
textLen = (short)GetHandleSize( rawTextHdl );
/* Actually create the token that we return */
tokDataPtr->textH = textH;
tokDataPtr->startPos = 0;
tokDataPtr->length = textLen; /* _All_ of the text */
tokDataPtr->propertyCode = typeNull; /* This means it's not a property */
return;
}
/* return a word token from a TextEdit text token. The word is actually still a TEText */
pascal OSErr WordFromTEText(DescType desiredClass,
const AEDesc *container,
DescType containerClass,
DescType form,
const AEDesc *selectionData,
AEDesc *theToken,
long LongInt)
{
AEDesc longKeyData;
WindowPtr wp;
long count;
TETextTokenBody tokData;
Handle rawTextHdl;
char *textPtr;
short i;
short textLen;
TEHandle textH;
short oldPos;
short newPos;
short offSet;
short oldLength;
OSErr err;
/* Check that the container is what we intend. This should only happen if we
* installed the token handler incorrectly.
*/
if ( container->descriptorType != typeTEText )
return errAEEventNotHandled;
/* find the text based on the key form */
switch ( form ){
case formAbsolutePosition:
/* Make sure we really have a type long descriptor */
err = AECoerceDesc( selectionData, typeLongInteger, &longKeyData );
if ( err ){
Gripe( "\pAECoerceDesc failed" );
return err;
}
count = **(long**)(longKeyData.dataHandle);
/* We're done with the descriptor created in the coercion */
err = AEDisposeDesc( &longKeyData );
if ( err ){
Gripe( "\pAEDisposeDesc failed" );
return err;
}
/* This is a really, really rude way to find a word. Assume that
* each word is separated by just one space (we will really want to
* allow for runs of spaces or other word-separating characters. The
* following method is basically just plain wrong, but will suffice for
* illustration.
*
* Also note that the first word in a block might come after a run of
* spaces.
*/
textH = (*(TETextTokenBody**)(container->dataHandle))->textH;
rawTextHdl = (Handle)TEGetText( textH );
oldPos = (*(TETextTokenBody**)(container->dataHandle))->startPos;
textPtr = *rawTextHdl + oldPos; /* Deref'ed a handle! */
oldLength = (*(TETextTokenBody**)(container->dataHandle))->length;
for ( i = oldLength ; i; i-- ){
if ( count == 0 ) /* Hmm... thought it was 1-based */
break;
if ( *textPtr++ == ' ' )
count--;
}
if ( i == 0 ){
Gripe( "\pRan off end of text before finding word" );
return errAENoSuchObject;
}
offSet = oldLength - i;
newPos = oldPos + offSet;
/* now find the end of the word */
textPtr = *rawTextHdl + newPos; /* Deref'ed a handle! */
for ( i = oldLength - offSet ; i; i-- ){
if ( *++textPtr == ' ' )
break;
}
/* Actually create the token that we return */
tokData.textH = textH;
tokData.startPos = newPos;
tokData.length = oldLength - offSet - i + 1;
tokData.propertyCode = typeNull; /* This means it's not a property */
err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
if ( err ){
Gripe( "\pAECreateDesc failed to create a token" );
return err;
}
return noErr;
break;
case formRelativePosition:
case formTest:
case formRange:
case formPropertyID:
case formName:
return errAEEventNotHandled; /* Flesh this out later */
break;
default:
Gripe( "\pGot unexpected key form" );
return errAEEventNotHandled;
}
return noErr;
}
/* Return a textedit char token given a textedit text token */
pascal OSErr CharFromTEText(DescType desiredClass,
const AEDesc *container,
DescType containerClass,
DescType form,
const AEDesc *selectionData,
AEDesc *theToken,
long LongInt)
{
AEDesc startSpec;
AEDesc endSpec;
AEDesc rangeRecord;
AEDesc startToken;
AEDesc endToken;
long count;
AEDesc longKeyData;
TEHandle textH;
Handle rawTextHdl;
long numRawChars;
TETextTokenBody tokData;
short offset;
OSErr err;
/* Check that the container is what we intend. This should only happen if we
* installed the token handler incorrectly.
*/
if ( container->descriptorType != typeTEText )
return errAEEventNotHandled;
/* find the text based on the key form */
switch ( form ){
case formAbsolutePosition:
/* Make sure we really have a type long descriptor */
err = AECoerceDesc( selectionData, typeLongInteger, &longKeyData );
if ( err ){
Gripe( "\pAECoerceDesc failed" );
return err;
}
count = **(long**)(longKeyData.dataHandle);
/* We're done with the descriptor created in the coercion */
err = AEDisposeDesc( &longKeyData );
if ( err ){
Gripe( "\pAEDisposeDesc failed" );
return err;
}
textH = (*(TETextTokenBody**)(container->dataHandle))->textH;
rawTextHdl = (Handle)TEGetText( textH );
numRawChars = GetHandleSize( rawTextHdl );
if ( count > 0 ){
/* The position is relative to the beginning */
if ( count > numRawChars ){
Gripe( "\pAsked for character past end" );
return errAENoSuchObject; /* Wanted char that was past end */
}
offset = (short) count;
} else {
/* In this case, the position is relative to the end of the text */
if ( count < -numRawChars ){
Gripe( "\pAsked for character before beginning" );
return errAENoSuchObject;
}
offset = (short)(numRawChars + count); /* Note that count is negative */
}
/* Actually create the token that we return. */
tokData.textH = textH;
tokData.startPos = offset;
tokData.length = 1; /* formAbs can have only 1 char */
tokData.propertyCode = typeNull; /* This means it's not a property */
err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
if ( err ){
Gripe( "\pAECreateDesc failed to create a token" );
return err;
}
return noErr;
break;
case formRelativePosition:
return errAEEventNotHandled;
break;
case formTest:
return errAEEventNotHandled;
break;
case formRange:
/* We must coerce the spec to an AERecrd so GetKeyDesc will know what
* to do with it.
*/
err = AECoerceDesc( selectionData,
typeAERecord,
&rangeRecord );
if ( err )
return err;
err = AEGetKeyDesc( &rangeRecord,
keyAERangeStart,
typeObjectSpecifier,
&startSpec );
if ( err ){
Gripe( "\pAEGetKeyDesc failed" );
return err;
}
err = AEGetKeyDesc( &rangeRecord,
keyAERangeStop,
typeObjectSpecifier,
&endSpec );
if ( err ){
Gripe( "\pAEGetKeyDesc failed" );
return err;
}
/* Now we have the object specifiers for the beginning and end of the
* range. We call AEResolve to give us tokens for the items that are
* at each end. Note that this causes two recursive calls to this very
* function.
*/
err = AEResolve( &startSpec, kAEIDoMinimum, &startToken );
if ( err )
return err;
/* 1.1.1 MDC fix a memory leak */
err = AEDisposeDesc( &startSpec );
if ( err )
return err;
err = AEResolve( &endSpec, kAEIDoMinimum, &endToken );
if ( err )
return err;
err = AEDisposeDesc( &endSpec );
if ( err )
return err;
/* Create the return token */
tokData.textH = (*(TETextTokenBody**)(container->dataHandle))->textH;
tokData.startPos = (*(TETextTokenBody**)(startToken.dataHandle))->startPos;
/* MDC 1.0d10 I have an off-by-one error in the speller's formAbsolutePosition
* relative to the end, that makes 0 be the last character. It should
* be -1. I should have added 1 here, which masked the bug.
*/
tokData.length = 1 + (*(TETextTokenBody**)(endToken.dataHandle))->startPos -
(*(TETextTokenBody**)(startToken.dataHandle))->startPos;
tokData.propertyCode = typeNull; /* This means it's not a property */
err = AECreateDesc( typeTEText, (Ptr)&tokData, sizeof( tokData ), theToken );
if ( err ){
Gripe( "\pAECreateDesc failed to create a token" );
return err;
}
return noErr;
break;
case formPropertyID:
return errAEEventNotHandled;
break;
case formName:
return errAEEventNotHandled; /* Flesh this out later */
break;
default:
Gripe( "\pGot unexpected key form" );
return errAEEventNotHandled;
}
return noErr;
}
/* Return a property token given a textedit text token.
* This works for any property of a textedit text item
*/
pascal OSErr PropFromTEText(DescType desiredClass,
const AEDesc *container,
DescType containerClass,
DescType form,
const AEDesc *selectionData,
AEDesc *theToken,
long LongInt)
{
OSErr err;
DescType propType;
/* Check that the container is what we intend. This should only happen if we
* installed the token handler incorrectly.
*/
if ( container->descriptorType != typeTEText )
return errAEEventNotHandled;
if ( form != formPropertyID ){
Gripe( "\pExpected formPropertyID" );
return errAEEventNotHandled;
}
propType = **( (DescType**)(selectionData->dataHandle) );
/* All we really do here is shove the property type into the token, if we
* know about the property type
*/
switch ( propType ){
case pBackgroundHilite:
/* This property contains a selection which is displayed while in the
* background. All we do here is set the property code in the token
* (This is done after the switch).
*/
break;
case pClass:
case pColor:
case pFont:
case pPointSize:
case pScriptTag:
case pTextStyles:
Gripe( "\pGot a property type we do not yet implement" );
return errAENoSuchObject;
break;
default:
Gripe( "\pUnknown property type" );
return errAENoSuchObject;
break;
}
/* All we do in the token is put the propType into it. We can start with the
* container descriptor as it has some of the fields filled in already.
*/
err = AEDuplicateDesc( container, theToken );
if ( err ){
Gripe( "\pAEDuplicateDesc failed" );
return err;
}
(*(TETextTokenBody**)(theToken->dataHandle))->propertyCode = propType;
return noErr;
}
/*
* Utilities for creating object specifiers
*/
OSErr CreateWindTextSpec( WindowPtr wp, long textNumber, AEDesc *specPtr )
{
WindowPtr candidate;
long windowNumber;
OSErr err;
/* Search through the open windows (yes I know there's only one) looking
* for the one matching the given pointer. This routine will be useful in
* multiple-window programs.
*/
if ( !wp )
return errAENoSuchObject;
candidate = FrontWindow();
if ( !candidate )
return errAENoSuchObject;
windowNumber = 1;
while ( candidate != wp ){
candidate = (WindowPtr)((WindowPeek)candidate)->nextWindow;
if ( !candidate )
return errAENoSuchObject;
windowNumber++;
}
err = CreateTextSpecifier( windowNumber, textNumber, specPtr );
return err;
}
OSErr CreateTextSpecifier( long windowNumber, long textNumber, AEDesc *specPtr )
{
AEDesc docSpecifier;
AEDesc textDescriptor;
OSErr err;
/* Create an object specifier for a particular text field within a particular
* window. In each case use formAbsolutePosition.
*/
err = BuildWindowSpecifier( &docSpecifier, windowNumber );
if ( err ){
Gripe( "\pBuildWindowDescriptor failed" );
return err;
}
err = CreateOffsetDescriptor( textNumber, &textDescriptor );
if ( err ){
Gripe( "\pCreateOffsetDescriptor failed" );
return err;
}
err = CreateObjSpecifier( cText,
&docSpecifier,
formAbsolutePosition,
&textDescriptor,
true, /* Dispose input descriptors */
specPtr );
if ( err ){
Gripe( "\pCreateObjSpecifer failed" );
return err;
}
return noErr;
}
/*
* Object counting routines.
*/
OSErr CountTextInWind( WindowPtr wp, long *countPtr )
{
/* For us, there can only be one text field in a window. If you have
* a structured document such as a spreadsheet, you should count the total
* number of text cells.
*/
*countPtr = 1L;
return noErr;
}
/*
* Coercion handlers
*/
/* Convert a pointer to raw text to a Pascal string. This is such a simple one it
* ought to have been built into the system!
*/
pascal OSErr TextPtrToPString( DescType typeCode,
Ptr dataPtr,
Size dataSize,
DescType toType,
long handlerRefCon,
AEDesc *resultPtr )
{
char *buf;
OSErr err;
Ptr myDataPtr;
Size myDataSize;
if ( toType != typePString )
return errAECoercionFail;
/* This one routine could reasonably coerce from several different sorts of textual
* data. We switch off the types and set a pointer to where the text data actually
* starts. Don't have any "from" types but typeChar so far.
*/
switch ( typeCode ){
case typeChar:
myDataPtr = dataPtr;
myDataSize = dataSize;
break;
default:
Gripe( "\pCannot coerce requested type" );
return errAECoercionFail;
break;
}
if ( myDataSize > 255 || myDataSize < 0 )
return errAECoercionFail;
buf = NewPtr( myDataSize + 1 );
if ( !buf )
return memFullErr;
buf[0] = myDataSize;
BlockMove( myDataPtr, &(buf[1]), dataSize );
err = AECreateDesc( typePString,
buf,
myDataSize + 1,
resultPtr );
return err;
}